%%%-------------------------------------------------------------------
%%% @author Rain
%%% @doc 帧同步房间进程相关接口
%%% @end
%%%-------------------------------------------------------------------
-module(ls_room_svr_util).
-feature(maybe_expr,enable).
-author("Rain").
-include("elock_step.hrl").
-include("ls_room_svr.hrl").

%% API
-export(
[
    client_call_change_scene_finish/1,
    client_call_reconnect/1,
    client_call_check_hash/3,
    client_call_frame_message/3
]).



-export(
[
    do_room_start/3,
    do_c_change_scene_finish/2,
    do_c_reconnect/2,
    do_ls_update/1,
    do_c_check_hash/4,
    do_c_frame_message/4
]).

%% @doc 客户端通知房间切换场景完成
client_call_change_scene_finish(RoleId) ->
    MatchId = ls_util:get_match_id(RoleId),
    case ls_util:get_room_pid(MatchId) of
        RoomPid when is_pid(RoomPid) ->
            erlang:send(RoomPid, {c_change_scene_finish, RoleId});
        _ ->
            elock_step_behaviour:call_client_room_not_find(RoleId, MatchId, change_scene),
            ok
    end.

%% @doc 客户端重连
client_call_reconnect(RoleId) ->
    MatchId = ls_util:get_match_id(RoleId),
    case ls_util:get_room_pid(MatchId) of
        RoomPid when is_pid(RoomPid) ->
            erlang:send(RoomPid, {c_reconnect, RoleId});
        _ ->
            elock_step_behaviour:call_client_room_not_find(RoleId, MatchId, reconnect),
            ok
    end.

%% @doc 客户端请求检查哈希值
client_call_check_hash(RoleId, Frame, Hash) ->
    MatchId = ls_util:get_match_id(RoleId),
    case ls_util:get_room_pid(MatchId) of
        RoomPid when is_pid(RoomPid) ->
            erlang:send(RoomPid, {c_check_hash, RoleId, Frame, Hash});
        _ ->
            elock_step_behaviour:call_client_room_not_find(RoleId, MatchId, check_hash),
            ok
    end.

%% @doc 客户端请求推送帧数据
client_call_frame_message(RoleId, CliFrame, LSInput) ->
    MatchId = ls_util:get_match_id(RoleId),
    case ls_util:get_room_pid(MatchId) of
        RoomPid when is_pid(RoomPid) ->
            erlang:send(RoomPid, {c_frame_message, RoleId, CliFrame, LSInput});
        _ ->
            elock_step_behaviour:call_client_room_not_find(RoleId, MatchId, frame_message),
            ok
    end.



%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%     ls_room_svr api functions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


%% @doc 获取指定帧数所在的时间戳
frame_time(Frame, #ls_fixed_time_counter{start_frame = StartFrame,
    start_time = StartTime, interval = Interval}) ->
    StartTime + (Frame - StartFrame) * Interval.


%% @doc 检查帧号是否合法
check_frame(Frame, MaxFrame) ->
    maybe
        false ?= Frame < 0,
        false ?= Frame > MaxFrame,
        true
    else
        _ ->
            false
    end.

%% @doc 确保帧号合法
ensure_frame(Frame, MaxFrame) ->
    case check_frame(Frame, MaxFrame) of
        false ->
            Msg = lists:flatten(io_lib:format("frame out: ~p, maxframe: ~p", [Frame, MaxFrame])),
            ?LS_ERROR(Msg),
            throw(Msg);
        _ ->
            true
    end.

%% @doc 获取已经输入的一帧的数据
%% return: #{RoleId => #ls_input{}}
get_frame_input(Frame, #ls_room_state{frame_buffer = FrameBuffer}) ->
    get_frame_input(Frame, FrameBuffer);
get_frame_input(Frame, #ls_frame_buffer{max_frame = MaxFrame,
    capacity = Capacity,
    frame_input = FrameInput}) ->
    ensure_frame(Frame, MaxFrame),
    maps:get(Frame rem Capacity, FrameInput, #{}).

%% @doc 帧数向前推进
move_forward(Frame, State = #ls_room_state{frame_buffer = LSFrameBuff =
    #ls_frame_buffer{max_frame = MaxFrame}}) ->
    maybe
        false ?= MaxFrame - Frame > ?LS_FRAME_COUNT_PER_SECOND,
        MaxFrame1 = MaxFrame + 1,
        LSFrameBuff1 = LSFrameBuff#ls_frame_buffer{max_frame = MaxFrame1},
        State#ls_room_state{frame_buffer = LSFrameBuff1}
    else
        _ ->
            State
    end.

%% @doc 获取某一帧的快照
get_snap_shot(Frame, #ls_room_state{frame_buffer = FrameBuffer}) ->
    #ls_frame_buffer{max_frame = MaxFrame, snap_shots_maps = SnapShotsMaps, capacity = Capacity} = FrameBuffer,
    ensure_frame(Frame, MaxFrame),
    Units = maps:get(Frame rem  Capacity, SnapShotsMaps, []),
    Units.

%% @doc 记录需要存档的数据
save_ls_world_and_record(#ls_room_state{authority_frame = Frame, frame_buffer = FrameBuffer,
    units = Units, reply = Reply} = State) ->
    #ls_frame_buffer{hashes = Hashes, snap_shots_maps = SnapShotsMaps} = FrameBuffer,
    #ls_reply{snap_shots = SnapShots} = Reply,
    {Hash, SnapShots1} = elock_step_behaviour:calc_frame_hash_and_snapshots(Units, SnapShots),
    Hashes1 = Hashes#{Frame => Hash},
    SnapShotsMaps1 = SnapShotsMaps#{Frame => Units},
    FrameBuffer1 = FrameBuffer#ls_frame_buffer{hashes = Hashes1, snap_shots_maps = SnapShotsMaps1},
    Reply1 = Reply#ls_reply{snap_shots = SnapShots1},
    State1 = State#ls_room_state{frame_buffer = FrameBuffer1, reply = Reply1},
    State1.



%% @doc 是否所有玩家都加载完成
is_all_load_finish(#ls_room_state{room_roles = RoomRoles}) ->
    is_all_load_finish1(RoomRoles).

is_all_load_finish1([]) ->
    true;
is_all_load_finish1([#ls_room_role{is_load_finish = false} | _]) ->
    false;
is_all_load_finish1([#ls_room_role{is_load_finish = true} | T]) ->
    is_all_load_finish1(T).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%     ls_room_svr handler functions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% TODO 如果是多人战斗的时候，某个人掉线了。到达了开启最后的时间预期，需要强制开启。
%% TODO 但是如果是1v1的情况，可能就是要强制退出房间重新匹配了。

%% @doc ls_room_svr 处理客户端切换场景结束
do_c_change_scene_finish(RoleId, #ls_room_state{room_roles = RoomRoles, random_seed = RandomSeed} = State) ->
    maybe
        LSRoomRole = #ls_room_role{} ?= lists:keyfind(RoleId, #ls_room_role.role_id, RoomRoles),
        LSRoomRole1 = LSRoomRole#ls_room_role{is_load_finish = true},
        RoomRoles1 = lists:keystore(RoleId, #ls_room_role.role_id, RoomRoles, LSRoomRole1),
        State1 = State#ls_room_state{room_roles = RoomRoles1},
        ?LS_MAYBE(not is_all_load_finish(State1), State1),

        StartTime = elock_step_behaviour:time_ms(),
        UnitInfos =
            [#ls_unit_info{role_id = TmpRoleId,
                position = #ls_pos_v3{x = 20, y = 0, z = 10},
                rotation = #ls_rotation{}}
                || #ls_room_role{role_id = TmpRoleId} <- RoomRoles1],

        room_bcast(RoomRoles, bcast_msg(call_client_fight_start, [StartTime, UnitInfos, RandomSeed])),
        erlang:send(self(), {room_start, StartTime, UnitInfos}), % 通知房间开启
        State1
    else
        #ls_room_state{} = RtnState ->
            RtnState;
        _ ->
            State
    end.


%% @doc ls_room_svr 处理客户端重连
do_c_reconnect(RoleId, State) ->
    #ls_room_state{authority_frame = Frame, units = UnitInfos,
        fixed_time_counter = #ls_fixed_time_counter{start_time = StartTime}} = State,
    elock_step_behaviour:call_client_reconnect_succ(RoleId, StartTime, UnitInfos, Frame),
    State.

%% @doc ls_room_svr 处理检查哈希
do_c_check_hash(RoleId, Frame, CliHash, #ls_room_state{frame_buffer = FrameBuffer} = State) ->
    #ls_frame_buffer{hashes = ServHashes, capacity = Capacity} = FrameBuffer,
    ServHash = maps:get(Frame rem Capacity, ServHashes, 0),
    case ServHash =/= CliHash of
        true ->
            Units = get_snap_shot(Frame, State),
            elock_step_behaviour:call_client_check_hash_fail(RoleId, Frame, Units),
            ok;
        _ ->
            pass
    end,
    State.

%% @doc ls_room_svr 处理客户端推送过来的帧消息
do_c_frame_message(RoleId, CliFrame, LSInput, State) ->
    #ls_room_state{authority_frame = AuthorityFrame,
        frame_buffer = FrameBuffer, fixed_time_counter = FixedTimeCounter} = State,
    #ls_frame_buffer{frame_input = FrameInput} = FrameBuffer,
    case CliFrame rem ?LS_FRAME_COUNT_PER_SECOND == 0 of
        true -> % 每经过20个帧,也就是1秒通知客户端同步一下速度
            NowFrameTime = frame_time(CliFrame, FixedTimeCounter),
            DiffTime = NowFrameTime - elock_step_behaviour:time_ms(),
            elock_step_behaviour:call_client_adjust_update_time(RoleId, DiffTime),
            ok;
        _ ->
            pass
    end,
    maybe
    % 小于AuthorityFrame，丢弃
        ?LS_MAYBE(CliFrame < AuthorityFrame,
            ?LS_WARN("RoleId ~p FrameMessage < AuthorityFrame
             CliFrame ~p LSInput ~p", [RoleId, CliFrame, LSInput])),

        % 大于AuthorityFrame + 10，丢弃
        ?LS_MAYBE(CliFrame > AuthorityFrame + 10,
            ?LS_WARN("RoleId ~p FrameMessage > AuthorityFrame + 10
             CliFrame ~p LSInput ~p", [RoleId, CliFrame, LSInput])),

        OneFrameInputs = get_frame_input(CliFrame, FrameBuffer),
        OneFrameInputs1 = OneFrameInputs#{RoleId => LSInput},
        FrameInput1 = FrameInput#{CliFrame => OneFrameInputs1},
        FrameBuffer1 = FrameBuffer#ls_frame_buffer{frame_input = FrameInput1},
        State#ls_room_state{frame_buffer = FrameBuffer1}
    else
        _ ->
            State
    end.


bcast_msg(MsgType, Data) ->
    #{msg_type => MsgType, data => Data}.

%% @doc 房间广播
room_bcast(#ls_room_state{room_roles = RoomRoles}, Msg) ->
    room_bcast(RoomRoles, Msg);
room_bcast(RoomRoles, Msg) ->
    Fun =
        fun(#ls_room_role{role_id = RoleId}) ->
            room_bcast_one(RoleId, Msg)
        end,
    lists:foreach(Fun, RoomRoles),
    ok.

room_bcast_one(RoleId, #{msg_type := call_client_fight_start, data := [StartTime, UnitInfos, RandomSeed]}) ->
    elock_step_behaviour:call_client_fight_start(RoleId, StartTime, UnitInfos, RandomSeed);
room_bcast_one(RoleId, #{msg_type := one_frame_input, data := OneFrameInputs}) ->
    elock_step_behaviour:call_client_one_frame_inputs(RoleId, OneFrameInputs);
room_bcast_one(_RoleId, Msg) ->
    ?LS_ERROR("Bcast Unknown Msg ~p", [Msg]),
    ok.



do_room_start(State, StartTime, UnitInfos) ->
    Frame = -1,
    FixedTimeCounter = #ls_fixed_time_counter{start_time = StartTime},
    Fun =
        fun(#ls_unit_info{role_id = RoleId}, {AccRoomRoles, AccMatchCnt}) ->
            AccRoomRoles1 = [#ls_room_role{role_id = RoleId} | AccRoomRoles],
            {AccRoomRoles1, AccMatchCnt + 1}
        end,
    {RoomRoles, MatchCnt} = lists:foldl(Fun, {[], 0}, UnitInfos),
    FrameBuffer = init_frame_buffer(Frame),
    State1 = State#ls_room_state{authority_frame = Frame, fixed_time_counter = FixedTimeCounter,
        reply = #ls_reply{unit_infos = UnitInfos}, frame_buffer = FrameBuffer,
        room_roles = RoomRoles, units = UnitInfos, match_cnt = MatchCnt
    },
    ls_update_timer(),
    State1.

init_frame_buffer(Frame) ->
    Capacity = ?LS_FRAME_COUNT_PER_SECOND * 60, % 60s
    MaxFrame = Frame + ?LS_FRAME_COUNT_PER_SECOND * 30, % 60s
    #ls_frame_buffer{max_frame = MaxFrame, capacity = Capacity}.

ls_update_timer() ->
    erlang:send_after(?LS_UPDATE_INTERVAL, self(), ls_update).

%% @doc 帧同步定时更新
do_ls_update(#ls_room_state{authority_frame = AuthorityFrame,
    fixed_time_counter = FixedTimeCounter} = State) ->
    ls_update_timer(),
    NowMs = elock_step_behaviour:time_ms(),
    AuthorityFrame1 = AuthorityFrame+1,
    maybe
        false ?= NowMs < frame_time(AuthorityFrame1, FixedTimeCounter), % 还没到下一帧的时间，太快了
        {OneFrameInputs, State1} = get_one_frame_message(AuthorityFrame1, State),
        State2 = State1#ls_room_state{authority_frame = AuthorityFrame1},
        Msg = bcast_msg(one_frame_input, OneFrameInputs),
        room_bcast(State2, Msg),
        update_one_frame_inputs(OneFrameInputs, State2)
    else
        _ ->
            State
    end.

%% @doc 获取一帧里面的从客户端来的存储下来的所有消息输入
get_one_frame_message(Frame, #ls_room_state{match_cnt = MatchCnt,
    room_roles = RoomRoles, frame_buffer = FrameBuffer} = State) ->
    OneFrameInputs = get_frame_input(Frame, State),
    State1 = move_forward(Frame, State),
    maybe
        ?LS_MAYBE(maps:size(OneFrameInputs) == MatchCnt, {OneFrameInputs, State1}),
        #ls_frame_buffer{max_frame = MaxFrame} = FrameBuffer,
        PreFrameInputs =
            case check_frame(Frame-1, MaxFrame) of
                true ->
                    get_frame_input(Frame-1, State); % 上一帧的数据
                _ ->
                    #{}
            end,
        % 有人输入的消息没过来，给他使用上一帧的操作
        OneFrameInputs1 = calc_one_frame_message(RoomRoles, OneFrameInputs, PreFrameInputs),
        {OneFrameInputs1, State1}
    end.

calc_one_frame_message([], OneFrameInputs, _PreFrameInputs) ->
    OneFrameInputs;
calc_one_frame_message([#ls_room_role{role_id = RoleId} | T], OneFrameInputs, PreFrameInputs) ->
    maybe
        false ?= maps:is_key(RoleId, OneFrameInputs),
        case maps:size(PreFrameInputs) > 0 andalso maps:get(RoleId, PreFrameInputs, undefined) of
            #ls_input{} = LSInput ->
                % 使用上一帧的输入
                OneFrameInputs1 = OneFrameInputs#{RoleId => LSInput};
            _ ->
                OneFrameInputs1 = OneFrameInputs#{RoleId => #ls_input{}}
        end,
        calc_one_frame_message(T, OneFrameInputs1, PreFrameInputs)
    else
        _ ->
            calc_one_frame_message(T, OneFrameInputs, PreFrameInputs)
    end.

%% @doc 更新一帧里面的输入
update_one_frame_inputs(OneFrameInputs, #ls_room_state{units = Units} = State) ->
    % 设置输入到每个LSUnit身上
    Units1 = update_one_frame_inputs1(Units, OneFrameInputs),
    State1 = State#ls_room_state{units = Units1},
    % 保存当前帧场景数据
    save_ls_world_and_record(State1).

update_one_frame_inputs1(Units, OneFrameInputs) ->
    update_one_frame_inputs1(Units, OneFrameInputs, []).
update_one_frame_inputs1([], _, Acc) ->
    lists:reverse(Acc);
update_one_frame_inputs1([#ls_unit_info{role_id = RoleId, position = OldPos} = LsUnit | T], OneFrameInputs, Acc) ->
    maybe
        #ls_input{position = #ls_pos_v2{x = X, y = Y}} = maps:get(RoleId, OneFrameInputs),
        false ?= X * X +  Y * Y < 0.0001,
        AddVector = #ls_vector{x = X, y = 0, z = Y},
        OldVector = #ls_vector{x = OldPos#ls_pos_v3.x, y = OldPos#ls_pos_v3.y, z = OldPos#ls_pos_v3.z},
        NewVector = ls_calc:add_vector(OldVector, AddVector),
        NewPos = #ls_pos_v3{x = NewVector#ls_vector.x, y = NewVector#ls_vector.y, z = NewVector#ls_vector.z},
        ForwardVector = ls_calc:subtract_vector(OldVector, NewVector),
        Rotation = calc_rotation_by_forward_vector(ForwardVector),
        LsUnit1 = LsUnit#ls_unit_info{position = NewPos, rotation = Rotation},
        update_one_frame_inputs1(T, OneFrameInputs, [LsUnit1 | Acc])
    else
        _ ->
            update_one_frame_inputs1(T, OneFrameInputs, [LsUnit | Acc])
    end.

%% @doc 根据朝向计算旋转角度
calc_rotation_by_forward_vector(#ls_vector{} = ForwardVector) ->
    UpVector = #ls_vector{x = 0, y = 1, z = 0},
    #ls_quaternion{x = RotationX, y = RotationY,
        z = RotationZ, w = RotationW} = ls_calc:look_rotation(ForwardVector, UpVector),
    #ls_rotation{x = RotationX, y = RotationY, z = RotationZ, w = RotationW}.

